import re

from typing import Optional
from urllib import parse as urllib_parse

from authlib.integrations.django_client import OAuth, DjangoOAuth2App

from apps.app_auth_provider.models import AuthProvider


# 扩展的“无空格文化”文字正则（CJK + 藏文 + 缅甸文）
_NO_SPACE_SCRIPTS_RE = re.compile(
    r'['
    r'\u4e00-\u9fff'      # CJK Unified Ideographs (中文汉字)
    r'\u3040-\u309f'      # Hiragana (日文平假名)
    r'\u30a0-\u30ff'      # Katakana (日文片假名)
    r'\uac00-\ud7af'      # Hangul Syllables (韩文)
    r'\u0f00-\u0fff'      # Tibetan (藏文)
    r'\u1000-\u109f'      # Myanmar (缅甸文)
    r']'
)

def has_no_space_script(text: str) -> bool:
    """判断文本是否包含应无空格书写的文字"""
    return bool(_NO_SPACE_SCRIPTS_RE.search(text))


class ProviderUserInfo:
    def __init__(
            self, external_id: str,
            true_name: str,
            email: str,
            org_name: str,
    ):
        self.external_id = external_id
        self.true_name = true_name if true_name else ''
        self.email = email if email else ''
        self.org_name = org_name if org_name else ''


class BaseProvider:
    def __init__(self, provider: AuthProvider):
        self.provider = provider
        self._oidc_client = None

    def _get_meta_api(self) -> str:
        return self.provider.get_metadata_url()

    def get_oidc_client(self) -> DjangoOAuth2App:
        if self._oidc_client is not None:
            return self._oidc_client

        oauth = OAuth()
        oauth.register(
            'oidc', client_id=self.provider.client_id, client_secret=self.provider.client_secret,
            server_metadata_url=self._get_meta_api(), client_kwargs={'default_timeout': (6, 20)}
        )
        self._oidc_client = oauth.create_client('oidc')
        return self._oidc_client

    def get_provider_metadata(self):
        aai_client = self.get_oidc_client()
        return aai_client.load_server_metadata()

    def build_authentication_url(
            self, state: str,
            nonce: Optional[str] = None,
            scope: Optional[str] = None,
            redirect_uri: Optional[str] = None
    ) -> str:
        """
        构建AAI登录url
        """
        if not redirect_uri:
            redirect_uri = self.provider.callback_url

        if not scope:
            scope = "openid email profile"

        if not nonce:
            nonce = state

        aai_client = self.get_oidc_client()
        rv = aai_client.create_authorization_url(
            response_type='code',
            redirect_uri=redirect_uri, state=state, nonce=nonce, scope=scope
        )
        return rv['url']

    def code2token(self, code: str) -> dict:
        aai_client = self.get_oidc_client()
        redirect_uri = self.provider.callback_url
        token = aai_client.fetch_access_token(code=code, redirect_uri=redirect_uri)
        return token

    def get_user_info(self, token: dict) -> ProviderUserInfo:
        """
        param token: OAuth2Token(dict)
        """
        raise NotImplementedError('NotImplemented get_user_info')

    def get_user_info_by_code(self, code: str) -> ProviderUserInfo:
        try:
            token = self.code2token(code=code)
        except Exception as exc:
            raise Exception(f'provider error, fetch_access_token,{str(exc)}') from exc

        try:
            return self.get_user_info(token=token)
        except Exception as exc:
            raise Exception(f'provider error, get userinfo,{str(exc)}') from exc

    @staticmethod
    def replace_query_params(url, params: dict) -> str:
        """
        替换或添加url中query参数的值
        :param url: url
        :param params: 键值对参数
        """
        (scheme, netloc, path, query, fragment) = urllib_parse.urlsplit(url)
        query_dict = urllib_parse.parse_qs(query, keep_blank_values=True)
        for key, val in params.items():
            query_dict[key] = [val]

        query = urllib_parse.urlencode(sorted(list(query_dict.items())), doseq=True)
        url = urllib_parse.urlunsplit((scheme, netloc, path, query, fragment))
        return str(url)

    @staticmethod
    def combine_full_name(last_name: str, first_name: str) -> str:
        """
        智能合并姓和名为全名，自动决定是否加空格。

        策略：
          - 如果姓或名中包含 CJK / 藏文 / 缅甸文等 → 默认不加空格
          - 否则 → 加空格

        :params last_name: 姓
        :params first_name: 名

        :return:
          str: 合并后的姓名
        """
        last = (last_name or "").strip()
        first = (first_name or "").strip()

        if not last and not first:
            return ""

        has_special_script = has_no_space_script(last) and has_no_space_script(first)

        if has_special_script:
            return last + first
        else:
            # 其他所有情况：英语、法语、阿拉伯语、泰语、越南语、俄语、蒙古语等 → 加空格
            return " ".join([part for part in [last, first] if part])
